#include "user_threads.h"
#include "list.h"
#include <unistd.h>
#include <setjmp.h>
#include <stdio.h>
#include "publicFunctions.h"
#include "user_io.h"
#include <signal.h>
#include <malloc.h>
#include <stdlib.h>

#define SYS_SCHED_YIELD	158

//number of list DS
#define LIST_COUNT	4
#define	TH_NOT_JOINED	0
#define TH_JOINED		1
#define TH_CANCEL_JOINED	2		//in case the thread is joined, and then canceled
#define NO_JOINED_TH	(-1)		//no thread is joind to this thread
#define JOINED_DEAD	(-2)		//a thread was joined, then died

#define TH_EMPTY		(-1)
#define TH_ACTIVE		0
#define TH_SUS			1
#define TH_ZOMBIE		2
//this is for the list of the threads needed to be cleared. this is not a state
#define TH_CLEAR		3
//number of threads
int initThreadNum = 0;

typedef struct user_thread_struct user_thread;
typedef user_thread* user_thread_p;

typedef struct thread_group_s threads_group;
typedef threads_group* thread_group_p;

struct user_thread_struct {
    uthread_id pid; //the tid of the current thread
    thread_group_p group; //the group this thread belongs to
    int* stackp; //begining of the stack
    jmp_buf env; //env stats for jumping between threads
    int state; //the state of this thread

    uthread_id joiningMe; //the thread joined me
    uthread_id meJoining; //the thread iam joining
    int joined_state; //the joiningMe state

    int retRoutineValue; //the return value of the thread
    UTHREAD_START_ROUTINE threadRoutine; //start routine for the thread
    void* arg; //argument for the start routine

    int wait_for_delete; //flag the thread for deletion
    int priority;
    int initPrio;
};

struct thread_group_s {
    list_group* th_list; //the group of thread lists
    threadList * lists[LIST_COUNT]; //the different lists
    user_thread_p current; //the current thread
    user_thread array[UTHREAD_MAX_THREADS]; //array of all the threads
    sigset_t signal_set; //a mask for the SIGALRM
    int size; //number of threads in this group
    //this counts for all threads that are not freed
    int need_resched; //set to one when a context switch is needed
};

threads_group main_group;

/**************************** Prototypes ******************************/

void alarm_handler(int signum);

void alarm_handler_masked(int signum);

void context_switch();

void printEnv(jmp_buf, uthread_id);

void print_thread_lists();

void dosomethingtofinish() {
    int t = 0;
    int y = 5;
    y=t;
    t=y;

}
/**************************** Some useful getters ****************************/

inline thread_group_p getThreadGroup() {
    return &main_group;
}
/*
 * search for thread by pid
 */
user_thread_p get_thread_by_pid(uthread_id pid) {
    user_thread_p thread;
    if (pid < 0 || pid >= UTHREAD_MAX_THREADS)
        return NULL;
    thread = (&getThreadGroup()->array[pid]);
    if (thread->state == TH_EMPTY)
        return NULL;
    return thread;
}

/**
 * Return the number of active thread in the group
 */
inline int get_active_count(thread_group_p group) {
    return get_list_size(group->lists[TH_ACTIVE]);
}

/*
 * a new tid is created for the thread
 */
uthread_id create_new_tid(thread_group_p group) {
    int tid = createNewElement(group->th_list);
    if (tid == -1)
        return TH_EMPTY;
    else
        return tid;
}

user_thread_p initThreadData(user_thread_p th,int prio) {
    user_thread_p runner;
    thread_group_p group = getThreadGroup();
    if (group->size == UTHREAD_MAX_THREADS)
        return NULL;
    int pid = create_new_tid(group);
    if (pid==TH_EMPTY)
        return NULL;
    group->size++;
    runner = (&getThreadGroup()->array[pid]);
    runner->group = group;
    runner->state = TH_ACTIVE;
    runner->pid = pid;
    runner->stackp = NULL;
    runner->priority = prio;
    runner->initPrio = prio;

    if (th != NULL) { //the thread is not the main thread
        runner->stackp = (int*) malloc(UTHREAD_DEF_STACK_SIZE);
        if (runner->stackp == NULL) {
            exit(-1);//the stack can't be allocated
        }
        runner->stackp += UTHREAD_DEF_STACK_SIZE / sizeof (int);
    }
    runner->joiningMe = NO_JOINED_TH;
    runner->meJoining = NO_JOINED_TH;
    runner->joined_state = TH_NOT_JOINED;
    runner->wait_for_delete = 0;
    return runner;
}

#define THREAD_CLEANED(thread)	(thread->stackp==NULL)

/**
 * Clears the threads that were deleted
 * The function assumes to be called when the alarm signal is masked
 */
void clear_threads() {
    thread_group_p group = getThreadGroup();
    user_thread_p thread;
    threadList* clear = group->lists[TH_CLEAR];
    int pid = get_element(clear);
    while (pid != EMPTY_LIST) {
        thread = (&getThreadGroup()->array[pid]); //get_thread_by_pid will not return the thread
        //main thread's stackp is NULL
        thread->stackp -= UTHREAD_DEF_STACK_SIZE / sizeof (int);
        free(thread->stackp);
        thread->stackp = NULL;
        if (thread->state == TH_ZOMBIE)
            move_element(clear, getThreadGroup()->lists[TH_ZOMBIE],
                thread->pid);
        else {
            removeNodeList(clear, pid);
            group->size--;
        }
        pid = get_element(clear);
    }
}

/**
 * Exit the library. The return value from the process is retval
 */
void exit_threads(int retval) {
    int j;
    disableSignals();
    thread_group_p group = getThreadGroup();
    if (group != NULL) {
        clear_threads();
        for (j = 0; j < LIST_COUNT; j++) {
            closeList(group->lists[j]);
        }
        closeListGroup(group->th_list);
    }
    exit(retval);
}

int create_thread_list(threadList** l) {
    return createNewList(getThreadGroup()->th_list, l);
}


int uthread_init() {
    int i, j;
    thread_group_p group = getThreadGroup();
    user_thread_p runner;
    if (initListGroup(&group->th_list, UTHREAD_MAX_THREADS) < 0) {
        exit(-1);//memory error
    }
    for (i = 0; i < UTHREAD_MAX_THREADS; i++) {
    	(&getThreadGroup()->array[i])->state = TH_EMPTY;
    }
    for (i = 0; i < LIST_COUNT; i++) {
        if (createNewList(group->th_list, &(group->lists[i])) != 0) {
            for (j = 0; j < i; j++) {
                closeList(group->lists[i]);
            }
            closeListGroup(group->th_list);
            exit(-1);//the scheduler list can't be created
        }
    }
    sigemptyset(&group->signal_set);
    sigaddset(&group->signal_set, SIGALRM);
    sigprocmask(SIG_UNBLOCK, &group->signal_set , NULL);
    group->size = 0;
    group->need_resched = 0;

    //create first thread
    runner = initThreadData(NULL,UTHREAD_MIN_PRIO);
    if (runner == NULL) {
        for (j = 0; j < LIST_COUNT; j++) {
            closeList(group->lists[i]);
        }
        closeListGroup(group->th_list);
        printf("couldn't create first thread\n");
        exit(-1);
    }
    addOrderedToList(group->lists[TH_ACTIVE], runner->pid);
    group->current = runner;
    initThreadNum = 1;
    signal(SIGALRM, alarm_handler);
    ualarm(UTHREAD_TIME_SLICE, UTHREAD_TIME_SLICE);
    disableSignals();
    return 0;
}

/***************************** Scheduler *****************************/

void disableSignals() {
    signal(SIGALRM, alarm_handler_masked);
}

void enableSignals() {
    if (getThreadGroup()->need_resched==1)
        context_switch();
    signal(SIGALRM, alarm_handler);
}

//return the threadList of the specified pid. make sure not to call this macro
//with a non existent thread
//#define THREAD_LIST(pid)	(get_list(getThreadGroup()->th_list, pid))

/**
 * Activates the thread in parameter
 * This is only for inside use. For external use - see wake_up
 * For any failure return UTHREAD_FAIL.
 * If the thread was activated successfully, or was already active,
 * returns UTHREAD_SUCCESS
 */
int activate_thread(user_thread_p thread) {
    threadList* l = (get_list(getThreadGroup()->th_list, thread->pid));
    if (l == NULL)
        return UTHREAD_FAIL; //thread is not in a threadList
    if (thread->state == TH_ACTIVE)
        return UTHREAD_SUCCESS; //thread is already active
    if (thread->state != TH_SUS)
        return UTHREAD_FAIL; //thread is not suspended -
    //couldn't activate
    if (thread->state == TH_SUS &&
                thread->wait_for_delete == 1)
        return UTHREAD_FAIL;    //being deleted
    removeFromList(l, thread->pid);

    addOrderedToList(getThreadGroup()->lists[TH_ACTIVE], thread->pid);
    thread->state = TH_ACTIVE;
    return UTHREAD_SUCCESS;
}

/**
 * defined in user_common.h
 */
int wakeupSuspended(uthread_id tid) {
    int result;
    user_thread_p thread;
    disableSignals();
    thread = get_thread_by_pid(tid);
    if (thread == NULL)
        return UTHREAD_INVALID;
    if (thread->joined_state != TH_NOT_JOINED) //do not try to wake up joines threads
        return UTHREAD_FAIL;
    result = activate_thread(get_thread_by_pid(tid));
    enableSignals();
    return result;
}

/**
 * Deactivates the thread in parameter
 * This is only for inside use. For external use - see suspend_thread()
 * For any failure return UTHREAD_FAIL.
 * If the thread was deactivated successfully, or was already suspended,
 * returns UTHREAD_SUCCESS
 */
int deactivateThread(user_thread_p thread) {
    threadList* l = (get_list(getThreadGroup()->th_list, thread->pid));
    if (l == NULL)
        return UTHREAD_FAIL;
    if (thread->state == TH_SUS)
        return UTHREAD_SUCCESS;
    if (thread->state != TH_ACTIVE)
        return UTHREAD_FAIL;
    move_element(l, getThreadGroup()->lists[TH_SUS], thread->pid);
    thread->state = TH_SUS;
    return UTHREAD_SUCCESS;
}

/**
 * defined in user_common.h
 */
int suspend_thread(uthread_id tid) {
    int result;
    user_thread_p thread;
    disableSignals();
    thread = get_thread_by_pid(tid);
    if (thread == NULL)
        return UTHREAD_INVALID;
    result = deactivateThread(get_thread_by_pid(tid));
    if (tid == getThreadGroup()->current->pid)
        context_switch();
    enableSignals();
    return result;
}

/**
 * defined in user_common.h
 */
int suspend_current_thread() {
    return suspend_thread(getThreadGroup()->current->pid);
}

/**
 * Moves the thread to the threadList of cleared thread - this is not the ZOMBIE state! for moving
 * to the ZOMBIE state, see end_thread()
 * The thread will be restored to the heap in the next context switch, so as to not free
 * the memory of the stack while using it
 */
void remove_thread(thread_group_p group, user_thread_p thread) {
    move_element((get_list(getThreadGroup()->th_list, thread->pid)), group->lists[TH_CLEAR],
            thread->pid);
    thread->state = TH_EMPTY;
}

/**
 * Look fo the next active thread in the specified group, after the current thread
 * If one is found, returns it, otherwise - current is the only active thread,
 * or there is no active thread - return NULL
 */
user_thread_p get_next_thread(thread_group_p group) {
    int elem = next_element(group->lists[TH_ACTIVE]);
    if (elem == EMPTY_LIST)
        return NULL;
    else
        return (&getThreadGroup()->array[elem]);
}

/**
 * Make the context switch between the current thread and the next one
 * The function assumes to be called when the alarm signal is masked
 */
void context_switch() {
    int pid;
    user_thread_p next_thread, current;
    thread_group_p group = getThreadGroup();
    current = group->current;
    if (sigsetjmp(current->env, 1) != 0) {
        if (get_list_size(group->lists[TH_CLEAR]) > 0)
            clear_threads();
        getThreadGroup()->need_resched = 0;
        return;
    }
    pid = get_element(group->lists[TH_ACTIVE]);
    while (pid == EMPTY_LIST) { //no running thread
        if (get_list_size(group->lists[TH_SUS]) == 0) {
            exit(current->retRoutineValue);
        }
        syscall(SYS_SCHED_YIELD);
        pid = get_element(group->lists[TH_ACTIVE]);
    }
    next_thread = (&getThreadGroup()->array[pid]);
    group->current = next_thread;
    siglongjmp(group->current->env, 1);
}

/**
 * Handles the alarm signal
 * To mask the signal use un\disableSignals()
 */
void alarm_handler(int signum) {
    thread_group_p group = getThreadGroup();
    user_thread_p current;
    current = group->current;
    if(current->priority > 0){
    	current->priority --;
    	return;
    }
    else
    {
    	current->priority = current->initPrio;
    }
    next_element(group->lists[TH_ACTIVE]);
    context_switch();
}

/**
 * Handles the alarm signal, when the signal is "masked" so as to when the signal
 * is unmasked, the library will know if there was an alarn signal, and so, make
 * a context switch
 */
void alarm_handler_masked(int signum) {
    getThreadGroup()->need_resched = 1;
}

/***************************** Library Functions *****************************/

void thread_start() {
    /* this is a special case where the context switch leads here
    because a context switch call is assumed to be inside a
    un\disableSignals() block, I unmask here. also I restore the
    alarm signal	*/
    dosomethingtofinish();

    user_thread_p curr = getThreadGroup()->current;
    int result;
    enableSignals(); //gets here after a context_switch
    result = curr->threadRoutine(curr->arg);
    uthread_exit(result); //doesn't return from here
}

uthread_id uthread_create(UTHREAD_START_ROUTINE start_routine, void* arg, int prio) {
    user_thread_p current, child;
    if (!initThreadNum)
        uthread_init();
    else
        disableSignals();
    current = getThreadGroup()->current;
    child = initThreadData(current,prio);
    if (child == NULL) { //the group is full
        enableSignals();
        return UTHREAD_FAIL;
    }
    addOrderedToList(getThreadGroup()->lists[TH_ACTIVE], child->pid);
    child->threadRoutine = start_routine;
    child->arg = arg;
    if (sigsetjmp(child->env, 1) != 0) {
        dosomethingtofinish();
        thread_start();
        exit(-1);// cant get here
    }
    //to save the stack size when routine called
    (child->env->__jmpbuf)[JB_SP] = (unsigned) child->stackp - 4 * sizeof (long int);
    (child->env->__jmpbuf)[JB_SP] = (unsigned) child->stackp - 4 * sizeof (long int);

    enableSignals();
    return child->pid;
}

void endThread(user_thread_p thread, int retval) {
    threadList* l = get_list(getThreadGroup()->th_list, thread->pid);
    thread_group_p group = thread->group;


    thread->wait_for_delete=1;
    thread->state = TH_SUS;
    move_element(l, group->lists[TH_SUS], thread->pid);
    enableSignals();
    //before deleting the thread, take it out of the mutex
    mutexQClean(thread->pid);
    disableSignals();
    thread->wait_for_delete=0;

    thread->state = TH_ZOMBIE;

    thread->retRoutineValue = retval;

    //check for a joiningMe thread
    if (thread->joiningMe == JOINED_DEAD) { //was joiningMe, but the joiningMe died
        //just kill this thread
        thread->state = TH_EMPTY;
    } else {
        if (thread->joiningMe != NO_JOINED_TH) {
            user_thread_p joiningMe = get_thread_by_pid(thread->joiningMe);
            if (joiningMe == NULL) { //must have a joining parent.else it's a bug
                exit(-2);
            }
            //the thread that is suspended and joining me, must return to running
            if (joiningMe->state == TH_SUS) {
                joiningMe->joined_state = TH_NOT_JOINED;
                /** hw3:mod **/
                joiningMe->meJoining = NO_JOINED_TH;
                /** END **/
                joiningMe->retRoutineValue = retval;
                activate_thread(joiningMe);
            }

        	if (thread->joined_state != TH_NOT_JOINED) {
        	    user_thread_p double_join = get_thread_by_pid(thread->meJoining);
        	    double_join->joiningMe = JOINED_DEAD;
       	 	}
            thread->state = TH_EMPTY;
        }
    }

    /** hw3:mod **/
    l = get_list(getThreadGroup()->th_list, thread->pid);
    /** END **/
    //send it to the CLEAR queue so it will be deleted
    move_element(l, group->lists[TH_CLEAR], thread->pid);
}

void uthread_exit(int retval) {
    thread_group_p group;
    user_thread_p current;
    if (!initThreadNum)
        exit(retval);//wanted to perform exit when not initialized?then exit
    group = getThreadGroup();
    current = group->current;
    disableSignals();
    endThread(current, retval);
    context_switch();
    //if we reached here then something wrong with the context switch
    //while(1);
    exit(-1);
}

uthread_id uthread_self() {
    uthread_id pid;
    if (!initThreadNum)
        uthread_init();
    else
        disableSignals();
    pid = getThreadGroup()->current->pid;
    enableSignals();
    return pid;
}

int uthread_join(uthread_id th, int* thread_return) {
    user_thread_p joinedThread;
    user_thread_p current;
    if (!initThreadNum)
        uthread_init();
    else
        disableSignals();
    joinedThread = get_thread_by_pid(th);
    if (joinedThread == NULL) {
        enableSignals();
        return UTHREAD_INVALID;
    }

    if (joinedThread->joiningMe != NO_JOINED_TH) { //already has a joiningMe joinedThread
        enableSignals();
        return UTHREAD_FAIL;
    }

    current = getThreadGroup()->current;

    if(current->joined_state == TH_JOINED){
        enableSignals();
        return UTHREAD_FAIL;
    }

    if (joinedThread == current) { //a thread cannot join itself
        enableSignals();
        return UTHREAD_FAIL;
    }
    if (joinedThread->state == TH_ZOMBIE) { //joining a dead thread
        if (thread_return != NULL)
            *thread_return = joinedThread->retRoutineValue;
        if (joinedThread->joined_state != TH_NOT_JOINED) {
            user_thread_p double_join = get_thread_by_pid(joinedThread->meJoining);
            double_join->joiningMe = JOINED_DEAD;
        }
        if (joinedThread->stackp==((void *)0)) //its memory was cleard
            removeNodeList((get_list(getThreadGroup()->th_list, joinedThread->pid)), th);
        else
            joinedThread->state = TH_EMPTY;
        getThreadGroup()->size--;
        enableSignals();
        return UTHREAD_SUCCESS;
    }
    current->joined_state = TH_JOINED;
    current->meJoining = joinedThread->pid;
    joinedThread->joiningMe = current->pid;
    deactivateThread(current);
    context_switch();
    if (thread_return != NULL)
        *thread_return = current->retRoutineValue;
    enableSignals();
    return UTHREAD_SUCCESS;
}

int uthread_cancel(uthread_id th) {
    user_thread_p thread;
    if (!initThreadNum) {         //this is the first thread
        if (th==0)              //cancel self
            exit(UTHREAD_CANCELLED);
        else
            return UTHREAD_INVALID;
    }
    disableSignals();

    thread = get_thread_by_pid(th);

    if(thread == NULL){
    	enableSignals();
    	return UTHREAD_INVALID;
    }

    if (thread == getThreadGroup()->current) {
        enableSignals();
        uthread_exit(UTHREAD_CANCELLED); //doesn't return from here
    }

    if (thread->state == TH_EMPTY || thread->state == TH_ZOMBIE) {
        enableSignals();
        return UTHREAD_FAIL;
    }
    if (thread->state == TH_SUS && thread->wait_for_delete==1){
        enableSignals();     //already being deleted -suspended to be deleted as shown in endThread
        return UTHREAD_FAIL;
    }
    //if got here, the thread is active or suspended
    endThread(thread, UTHREAD_CANCELLED);
    //clearing the memory of freed threads
    if ((getThreadGroup()->current)->state == TH_ACTIVE)
        clear_threads();
    enableSignals();
    return UTHREAD_SUCCESS;
}

void uthread_yield() {
    thread_group_p group;
    user_thread_p current;
    if (!initThreadNum)
        uthread_init();
    else
        disableSignals();
    group = getThreadGroup();
    if (get_active_count(group) == 1) {
        enableSignals();
        return;
    }
    current = getThreadGroup()->current;
    next_element(group->lists[TH_ACTIVE]);
    context_switch(); //frees the disableSignals
    enableSignals();
    return;
}

/************************** Print Functions **************************/

void printEnv(jmp_buf env, uthread_id pid) {
    printf("REG of thread %d\nSP: %.8X\nPC: %.8X\nBP: %.8X\n",
            pid, env->__jmpbuf[JB_SP],
            env->__jmpbuf[JB_PC], env->__jmpbuf[JB_BP]);
    printf("-----------------\n");
}

void print_thread_lists() {
    thread_group_p group;
    disableSignals();
    group = getThreadGroup();
    printf("ACTIVE: \n");
    print_list(group->lists[TH_ACTIVE]);
    printf("SUSPENDED: \n");
    print_list(group->lists[TH_SUS]);
    printf("ZOMBIE: \n");
    print_list(group->lists[TH_ZOMBIE]);
}



